package logic;

import static view.Door.dir;
import static view.Door.runing;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.swing.JScrollBar;
import javax.swing.JTextArea;
import entity.DefinedRuntimeException;
import entity.FileInfo;
import tools.CloseSource;

/**
 * 根据MD5去重.
 * @author Rex
 *
 */
public class DuplicateRemoval {
	
	/**
	 * MD5摘要对象.
	 */
	private static MessageDigest digest;
	
	/**
	 * 根据MD5去重.
	 * @param msg
	 * @param bar 
	 */
	public static void action(JTextArea msg, JScrollBar bar) throws DefinedRuntimeException {
		if (runing == false) {
			if (dir != null) {
				runing = true;
				// 提示用户
				Thread t = new Thread(() -> msg.append("请稍候，去重会消耗较长时间……"));
				t.start();

				// 等待线程t1执行完毕后，其它线程再继续执行
				try {
					t.join();
				} catch (InterruptedException ex) {
					msg.append("线程调度失败");
					throw new DefinedRuntimeException("线程调度失败", ex);
				}

				// 客户压力测试
				new Thread(() -> DuplicateRemoval.startup(msg, bar)).start();
			}
		} else {
			msg.append("程序正在执行，请勿重复点击");
		}
		
		// 拖动滚动条至最后一行
		bar.setValue(bar.getMaximum());
	}

	private static void startup(JTextArea msg, JScrollBar bar) throws DefinedRuntimeException {
		try {
			// 计算所选文件夹内所有文件的MD5（不递归子文件夹）
			List<FileInfo> md5s = getMD5s(dir);
			
			// 统计每个MD5值出现的次数
			int size = 0;
			Map<String, Integer> map = null;
			if (md5s != null && (size = md5s.size()) != 0) {
				map = new HashMap<>(size / 3 * 4 + 1);
				for (int i = 0; i < size; i++) {
					String md5 = md5s.get(i).getMd5();
					Integer value = map.get(md5);
					if (value == null) {
						map.put(md5, 1);
					} else {
						map.put(md5, ++value);
					}
				}
			}
			
			// 挑选出重复出现的MD5值
			List<String> temp = null;
			if (map != null) {
				temp = new ArrayList<>();
				for (Entry<String, Integer> entry : map.entrySet()) {
					if (entry.getValue() > 1) {
						temp.add(entry.getKey());
					}
				} 
			}
			
			// 挑选出重复的文件。重复的文件需要保留一个
			List<File> result = null;
			if (temp != null) {
				result = new ArrayList<>();
				int len = temp.size();
				for (int i = 0; i < len; i++) {
					String md5 = temp.get(i);
					int times = 0;
					for (int j = 0; j < size; j++) {
						FileInfo fi = md5s.get(j);
						if (fi.getMd5().equals(md5)) {
							times++;
							if (times>1) {
								result.add(fi.getFile());
							}
						}
					}
				} 
			}
			
			// 重新归档重复的文件
			if (result != null) {
				int length = result.size();
				for (int i = 0; i < length; i++) {
					File file = result.get(i);
					FilesClassify.classify("重复的文件", file, file.getName());
				}
			}
			
			//完成后提示用户
			msg.append("去重完成");
		} catch (DefinedRuntimeException e) {
			msg.append(e.getMessage());
			throw e;
		} catch (Exception e) {
			msg.append("遭遇未知异常");
			throw new DefinedRuntimeException("未知的异常", e);
		} finally {
			runing = false;
			// 拖动滚动条至最后一行
			bar.setValue(bar.getMaximum());
		}
	}
	
    /** 
     * 计算文件的MD5.
     * @param file 
     * @return 
     */  
	private static List<FileInfo> getMD5s(File files) throws DefinedRuntimeException {
		// 结果
		List<FileInfo> list = null;

		// 扫描文件夹内文件
		File[] fs = files.isDirectory() ? files.listFiles() : new File[] { files };
		if (fs != null) {
			// 初始化必要变量
			int size = fs.length;
			FileInputStream[] fis = new FileInputStream[size];
			list = new ArrayList<>(size);
			try {
				// 此变量在初始化时可能抛出异常，需在try-catch块内完成初始化
				digest = digest == null ? MessageDigest.getInstance("MD5") : digest;

				// 遍历所有文件
				for (int i = 0; i < size; i++) {
					File file = fs[i];
					if (file.isFile()) {
						fis[i] = new FileInputStream(file);

						// 开始计算文件的MD5值
						byte buffer[] = new byte[1024];
						int len;
						while ((len = fis[i].read(buffer, 0, 1024)) != -1) {
							digest.update(buffer, 0, len);
						}
						String md5 = new BigInteger(1, digest.digest()).toString(16);

						// 加入结果
						list.add(new FileInfo(file, md5));
					}
				}
			} catch (NoSuchAlgorithmException e) {
				throw new DefinedRuntimeException("MD5摘要对象异常", e);
			} catch (IOException e) {
				throw new DefinedRuntimeException("文件读取异常", e);
			} catch (Exception e) {
				throw new DefinedRuntimeException("计算MD5时遭遇未知异常", e);
			} finally {
				CloseSource.close(fis);
			}
		}

		return list;
	}
	
}